/* PC frontend for LOCKJAW, an implementation of the Soviet Mind Game

Copyright (C) 2006-2008 Damian Yerrick <tepples+lj@spamcop.net>

This work is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

Original game concept and design by Alexey Pajitnov.
The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
or The Tetris Company LLC.

*/

package fizzpicks.tetrapulp.tetrapulp;

import net.jscience.math.MathFP;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.util.*;
import java.util.List;

import pulpcore.image.CoreFont;
import pulpcore.image.CoreImage;
import pulpcore.CoreSystem;
import pulpcore.Assets;
import pulpcore.Input;

/* Header for PC frontend for LOCKJAW, an implementation of the Soviet Mind Game

Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>

This work is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

Original game concept and design by Alexey Pajitnov.
The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
or The Tetris Company LLC.

*/

class LJPCSkin {
  Image bg;
  Image blocks;
  Image connBlocks;
  int blkW, blkH;
  int baseX, baseY;
  int pfElev;
  int transparentPF;
  LJNEXT nextPos;
  boolean shiftScale;
  LJMusic bgm;
}

class LJPCView
{
  BufferedImage back;
  LJPCSkin skin;
  int baseX;
  int nextAbove;  // number of next pieces above shadow

  // Platform-dependent sound
  int b2bcd1, b2bcd2;

  boolean wantRecord;
}

enum LJNEXT {
  RIGHT,
  RIGHT_TAPER,
  TOP,
    NONE
}

/**
 * The number of persistent preferences.  Must match the number of
 * fields in struct LJPrefsNamed.
 */

/**
 * Names of persistent preferences.
 * Order must be exactly the same as in
 * optionsMenu[] (options.c)
 */
enum PCOPTIONS {
  NEXT_ABOVE,
  TRAILS,
  AUTO_PAUSE,
  AUTO_RECORD,
  WINDOWED,
}

class LJPrefs {
    Map<PCOPTIONS,Boolean> number = new EnumMap<PCOPTIONS,Boolean>(PCOPTIONS.class);
}


/**
 * Created by will
 * Date: May 3, 2008 8:41:36 AM
 */
public class LJPC extends Gimmicks {

    public static boolean wantsClose = false;

static String LJ_VERSION = "0.45 (" + ")";
public static final int USE_PICS_FOLDER = 0;

/* set by display mode */
static int bgColor, fgColor = 0, hiliteColor, pfBgColor = 0, pfFgColor;
static volatile int curTime = 0;
static volatile boolean redrawWholeScreen = true;
static volatile boolean wantPause = false;
static final File dat = null, sound_dat = null;
static CoreFont aver32 = null, aver16 = null;
static boolean autoPause;
int refreshRate = 0;
String demoFilename;

static int withPics = -1;

String skinName = "default.skin";
static String ljblocksSRSName;
static String ljblocksSegaName;
static String ljconnSRSName;
static String ljconnSegaName;
static String ljbgName;
static String menubgName;
static String bgmName;
static String bgmRhythmName;
static long bgmLoopPoint = 0;
static boolean bgmReadyGo = false;
static int bgmVolume = 128;
static int skinW = 800;
static int skinH = 600;
    private static final int EXIT_FAILURE = -1;

    static void incCurTime() {
  ++curTime;
}

static void amnesia() {
  redrawWholeScreen = true;
}

static void requestPause() {
  wantPause |= autoPause;
}


int getTime() {
  return curTime;
}

void yieldCPU() {
    try {
        Thread.sleep(10);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

void ljBeginDraw(LJView v, boolean sync) {
  if(sync) vsync();
  acquire_screen();
  if (redrawWholeScreen) {
    redrawWholeScreen = false;
    playRedrawScreen(v);
  }

  // Draw shape
  blit(v.plat.skin.bg, screen, 0, 0, 0, 0, 48, 16);
  if (v.control.replayDst != null) {

    // circle: record
    circlefill(screen, 8, 8, 7, fgColor);
    textout_ex(screen, aver16, "Rec", 18, 2, fgColor, -1);
  } else if (v.control.replaySrc != null) {

    // triangle: play
    for (int wid = 0; wid < 7; ++wid) {
      hline(screen, 2, 2 + wid, wid * 2 + 3, fgColor);
      hline(screen, 2, 14 - wid, wid * 2 + 3, fgColor);
    }
    textout_ex(screen, aver16, "Play", 18, 2, fgColor, -1);
  } else {

    // square: stop
    rectfill(screen, 2, 2, 14, 14, fgColor);
  }
}

    void ljEndDraw(LJView v) {
  release_screen();
}

static void startRecording(LJView v) {
  withPics = -1;

  // close existing playback
  if (v.control.replaySrc != null) {
    replayClose(v.control.replaySrc);
    v.control.replaySrc = null;
  }

  // toggle recording
  if (v.control.replayDst != null) {
    replayClose(v.control.replayDst);
    v.control.replayDst = null;
  } else {
    v.control.replayDst = newReplay("demo.ljm", v.field);
//#if USE_PICS_FOLDER
    withPics = 0;
//#endif
  }
}

    private static LJReplay newReplay(String s, LJField field) {
        return null;
    }

    private static void replayClose(LJReplay replaySrc) {
    }

    static void startPlaying(LJView v) {
  withPics = -1;

  // close existing recording
  if (v.control.replayDst != null) {
    replayClose(v.control.replayDst);
    v.control.replayDst = null;
  }

  // toggle playback
  if (v.control.replaySrc != null) {
    replayClose(v.control.replaySrc);
    v.control.replaySrc = null;
  } else {
    String path = "";

      v.control.replaySrc = openReplay("demo.ljm", v.field);
    v.nLockTimes = 0;
//#if USE_PICS_FOLDER
    withPics = 0;
//#endif
  }
  v.backDirty.fill();
  v.field.reloaded = 1;
}

    private static LJReplay openReplay(String s, LJField field) {
        return null;
    }

    boolean ljHandleConsoleButtons(LJView v) {
  boolean canceled = false;

/*
  while (keypressed()) {
    int scancode = 0;
    int codepoint = ureadkey(scancode);

    if (scancode == KEY_ESC) {
      wantPause = true;
    } else if (scancode == KEY_PRTSCR) {
      saveScreen(-1);
    } else if (codepoint == '[') {
      v.plat.wantRecord = true;
    } else if (codepoint == ']') {
      v.plat.wantRecord = false;
      startPlaying(v);
    }
  }
*/
  if (v.plat.wantRecord) {
    v.plat.wantRecord = false;
    startRecording(v);
  }
  if (wantPause) {
    canceled = pauseGame(v.plat);
    wantPause = false;
  }

  if (wantsClose) {
    canceled = true;
  }

  return canceled;
}


static void drawBlock(final LJPCView v, int x, int y, int blk) {

  if (x >= 0 && x < LJ_PF_WID && y >= 0 && y < LJ_PF_VIS_HT) {
    final int w = v.skin.blkW;
    final int h = v.skin.blkH;
    final int dstX = w * x;
    final int dstY = h * (LJ_PF_VIS_HT - 1 - y);

    if (v.skin.transparentPF != 0 && (blk == 0 || blk == 0x100)) {

      // Copy background
      final int srcX = dstX + v.baseX;
      final int srcY = dstY + v.skin.baseY
                                - LJ_PF_VIS_HT * h - v.skin.pfElev;
      blit(v.skin.bg, v.back, srcX, srcY, dstX, dstY, w, h);
    } else if (blk != 0 && v.skin.connBlocks != null) {

      // Copy connected block
      final int srcX = ((blk >> 0) & 15) * w;
      final int srcY = ((blk >> 4) & 15) * h;
      blit(v.skin.connBlocks, v.back, srcX, srcY, dstX, dstY, w, h);
    } else {

      // Copy lone block
      final int srcX = ((blk >> 4) & 7) * w;
      final int srcY = ((blk >> 7) & 1) * h;
      blit(v.skin.blocks, v.back, srcX, srcY, dstX, dstY, w, h);
    }

    // 0x100: hotline
    if ((blk & 0x100) != 0) {
      hline(v.back, dstX, dstY + h / 2 - 1, dstX + w - 1, pfFgColor);
      hline(v.back, dstX, dstY + h / 2    , dstX + w - 1, pfFgColor);
      hline(v.back, dstX, dstY + h / 2 + 1, dstX + w - 1, pfBgColor);
    }
  }
}

/**
 * Draws multiple rows of the playfield
 * @param v     the playfield
 * @param rows  the rows to be updated (0x00001 on bottom, 0x80000 on top)
 */
void updField(final LJView v, LJBits rows) {
  final LJField p = v.field;

  acquire_bitmap(v.plat.back);
  for (int y = 0;
       y < LJ_PF_VIS_HT && !rows.isEmpty();
       ++y) {
    int blankTile = 0;

    // hotline: draw 0x100 as the background
    if (hotlineRows[y]!=0 && v.field.scoreStyle == LJSCORE.HOTLINE) {
      blankTile = 0x100;
    }
    // during line clear delay, draw bars to show which lines were cleared
    if (p.state == LJState.LINES_FALLING && p.stateTime > 0 && p.tempRows.get(y)) {
      blankTile = 0x100;
    }
    if (rows.get(y)) {
      for (int x = p.leftWall; x < p.rightWall; x++) {
        int b = v.hidePF ? 0 : p.b[y][x];
        drawBlock(v.plat, x, y, b != 0 ? b : blankTile);
      }
    }
  }
  release_bitmap(v.plat.back);
}


public static final int SHADOW_BLOCK = 0x00;

/**
 * Draws a tetromino whose lower left corner of the bounding box is at (x, y)
 * @param b the bitmap to draw to
 * @param piece the piece to be drawn
 * @param x distance from to left side of 4x4 box
 * @param y distance from top of bitmap to bottom of 4x4 box
 * @param theta the rotation state (0: U; 1: R; 2: D; 3: L; 4: Initial position)
 * @param w width of each block
 * @param h height of each block
 * @param color Drawing style
 * color == 0: draw shadow
 * color == 0x10 through 0x70: draw in that color
 * color == 0x80: draw as garbage
 * color == -255 through -1: draw with 255 through 1 percent lighting
 */
LJBits drawPiece(final LJView v, final Image b,
                 LJP piece, int x, int y, int theta,
                 int color, int w, int h) {
  // Don't try to draw the -1 that's the sentinel for no hold piece
  if (piece == LJP.NONE)
    return null;

  LJBits rows = new LJBits();
  LJBlkSpec[] blocks = new LJBlkSpec[4];
  final int srcW = v.plat.skin.blkW;
  final int srcH = v.plat.skin.blkH;
  Image singleBlocks = v.plat.skin.blocks;
  int singleSeries = (color == 0
                      && singleBlocks.getHeight(null) >= 6 * srcH)
                     ? 4 : 2;

  // color: 0 for shadow, >0 for piece
  Image connBlocks = (color != SHADOW_BLOCK)
                       ? v.plat.skin.connBlocks : null;

  Image transTemp = color < 0
                      || (color == SHADOW_BLOCK && v.hideShadow.trans)
                      ? create_bitmap(w, h) : null;

  if (transTemp != null) {
    set_trans_blender(0, 0, 0, v.hideShadow != LJSHADOW.NONE ? 128 : 64);
  }

  expandPieceToBlocks(blocks, v.field, piece, 0, 0, theta);
  acquire_bitmap(b);
  for (int blk = 0; blk < 4; ++blk) {
    int blkValue = blocks[blk].conn;

    // If blkValue == 0 then the block is not part of the piece,
    // such as if it's a domino or tromino or (once pentominoes
    // are added) a tetromino.
    if (blkValue != 0) {
      int blkX = blocks[blk].x;
      int blkY = blocks[blk].y;
      final int dstX = x + w * blkX;
      final int dstY = y + h * (-1 - blkY);

      if (color > 0) {
        blkValue = color | (blkValue & CONNECT_MASK);
      } else if (color == 0 && v.hideShadow == LJSHADOW.COLORLESS) {
        blkValue = 0;
      }

      if (connBlocks != null) {
        final int srcX = ((blkValue >> 0) & 15) * srcW;
        final int srcY = ((blkValue >> 4) & 15) * srcH;

        if (transTemp != null) {
          stretch_blit(connBlocks, transTemp,
                       srcX, srcY, srcW, srcH,
                       0, 0, w, h);
          if (color < 0) {
            draw_lit_sprite(b, transTemp, dstX, dstY, color + 256);
          } else {
            draw_trans_sprite(b, transTemp, dstX, dstY);
          }
        } else if (srcW != w || srcH != h) {
          stretch_blit(connBlocks, b,
                       srcX, srcY, srcW, srcH,
                       dstX, dstY, w, h);
          if (dstY > 600) {
            allegro_message("dstY = "+dstY+"\n");
          }
        } else {
          blit(connBlocks, b,
               srcX, srcY,
               dstX, dstY, w, h);
        }
      } else {
        final int srcX = ((blkValue >> 4) & 7) * srcW;
        final int srcY = (((blkValue >> 7) & 1) + singleSeries) * srcH;

        if (transTemp != null) {
          stretch_blit(singleBlocks, transTemp,
                       srcX, srcY, srcW, srcH,
                       0, 0, w, h);
          if (color < 0) {
            draw_lit_sprite(b, transTemp, dstX, dstY, color + 256);
          } else {
            draw_trans_sprite(b, transTemp, dstX, dstY);
          }
        } else {
          stretch_blit(singleBlocks, b,
                       srcX, srcY, srcW, srcH,
                       dstX, dstY, w, h);
        }
      }
      rows.set(blkY);
    }
  }
  release_bitmap(b);
  if (transTemp != null) {
    solid_mode();
    transTemp.flush();
  }

  return rows;
}

    private void set_trans_blender(int i, int i1, int i2, int i3) {

    }

    private void solid_mode() {

    }

    private void draw_trans_sprite(Image b, Image transTemp, int dstX, int dstY) {

    }

    private void stretch_blit(Image singleBlocks, Image transTemp, int srcX, int srcY, int srcW, int srcH, int i, int i1, int w, int h) {
    }

    private void draw_lit_sprite(Image b, Image transTemp, int dstX, int dstY, int i) {
    }

    void drawPieceInPF(LJView v, int dstX, long dstY, int theta, int color) {
  LJBits bits;
  Image b = v.plat.back;
  final LJField p = v.field;
  int y = (int)MathFP.toLong(dstY);
  final int w = v.plat.skin.blkW;
  final int h = v.plat.skin.blkH;
  int drawnY = (int)MathFP.mul(h, dstY);

  bits = drawPiece(v, b, p.curPiece[0],
                   w * dstX, h * LJ_PF_VIS_HT - drawnY,
                   theta,
                   color, w, h);
  bits.bits = (y >= 0) ? bits.bits << y : bits.bits >> -y;
  bits.bits &= (1 << LJ_PF_VIS_HT) - 1;
  if ((dstY & 0xFFFF) != 0) {
    bits.bits |= bits.bits << 1;
  }

  v.backDirty.add(bits);
  v.frontDirty.add(bits);
}


void drawFallingPiece(LJView v) {
  final LJField p = v.field;
  LJP piece = p.curPiece[0];
  int y = v.smoothGravity
          ? (int)p.y
          : (int)MathFP.toFP(MathFP.toLong(p.y));
  final int color = (p.state == LJState.LANDED)
                    ? -128 - ((p.stateTime + 1) * 128 / (p.speed.lockDelay + 1))
                    : pieceColors[piece.index];

  // Draw trails
  if (v.showTrails) {

    // trail going up
    while (v.trailY - y < MathFP.toFP(-1)) {
      v.trailY += MathFP.toFP(1);
      drawPieceInPF(v, p.x, v.trailY, p.theta, color);
    }

    // trail going down
    while (v.trailY - y > MathFP.toFP(1)) {
      v.trailY -= MathFP.toFP(1);
      drawPieceInPF(v, p.x, v.trailY, p.theta, color);
    }
  }
  drawPieceInPF(v, p.x, y, p.theta, color);
  v.trailY = y;
}

void drawShadow(LJView v) {
  final LJField p = v.field;
  int y = (int)MathFP.toFP(p.hardDropY);
  final int color = SHADOW_BLOCK;
  drawPieceInPF(v, p.x, y, p.theta, color);
}

void drawNextPieces(LJView v) {
  int baseX = v.plat.baseX;
  int baseY = v.plat.skin.baseY - v.plat.skin.pfElev;
  int blkW = v.plat.skin.blkW;
  int blkH = v.plat.skin.blkH;
  int holdPieceColor = v.field.alreadyHeld
                       ? 0x80
                       : pieceColors[v.field.holdPiece.index];
  int ceil = v.field.ceiling;

  acquire_screen();

  if (v.frontDirty.dirtyNext) {
    int holdW = blkW * 2 / 3;
    int holdH = blkH * 2 / 3;
    int holdX = baseX + blkW;
    int holdY = baseY - (ceil - 1) * blkH - 4 * holdH;

    // Move the hold piece within the screen
    if (holdY < 0) {
      holdY = 0;
    }

    // Draw hold piece
    blit(v.plat.skin.bg, screen,
         holdX, holdY,
         holdX, holdY,
         holdW * 4, holdH * 2);
    drawPiece(v, screen,
              v.field.holdPiece, holdX, holdY + 4 * holdH, 4,
              holdPieceColor, holdW, holdH);
  }
  // Draw next pieces

  switch (v.plat.skin.nextPos) {
  case RIGHT:
  case RIGHT_TAPER:
    if (v.frontDirty.dirtyNext) {
      int y = baseY - ceil * blkH;
      int x = baseX + LJ_PF_WID * blkW;
      int w = blkW;
      int h = blkH;
      for (int i = 1; i <= v.nextPieces; ++i) {
        LJP piece = v.field.curPiece[i];

        blit(v.plat.skin.bg, screen, x, y, x, y, w * 4, h * 2);
        if (!v.hideNext) {
          drawPiece(v, screen,
                    piece, x, y + 4 * h, 4,
                    pieceColors[piece.index], w, h);
        }
        y += 8 + h * 2;
        if (v.plat.skin.nextPos == LJNEXT.RIGHT_TAPER) {
          --h;
          --w;
        }
      }
    }
    break;

  case TOP:
    if (v.frontDirty.dirtyNext) {
      int y = baseY - (ceil + 2) * blkH - 8;
      int x = baseX + 4 * blkW;

      blit(v.plat.skin.bg, screen, x, y, x, y, blkW * 8, blkH * 2);
      if (!v.hideNext) {
        if (v.nextPieces >= 1) {
          LJP piece = v.field.curPiece[1];
          drawPiece(v, screen,
                    piece, x, y + 4 * blkH, 4,
                    pieceColors[piece.index], blkW, blkH);
        }
        if (v.nextPieces >= 2) {
          LJP piece = v.field.curPiece[2];
          x += 4 * blkW;
          drawPiece(v, screen,
                    piece, x, y + 3 * blkH, 4,
                    pieceColors[piece.index], blkW/ 2, blkH / 2);
        }
        if (v.nextPieces >= 3) {
          LJP piece = v.field.curPiece[3];
          x += 2 * blkW;
          drawPiece(v, screen,
                    piece, x, y + 3 * blkH, 4,
                    pieceColors[piece.index], blkW / 2, blkH / 2);
        }
      }
    }
    break;
  }

  if (v.plat.nextAbove != 0 && !v.hideNext) {
    int row = (v.field.hardDropY + 4);
    int x = (1 + v.field.x) * blkW;
    for (int i = 1;
         i <= v.plat.nextAbove
         && row <= v.field.ceiling - 2;
         ++i) {
      int y = (LJ_PF_VIS_HT - row) * blkH;
      LJP piece = v.field.curPiece[i];

      drawPiece(v, v.plat.back,
                piece, x, y, 4,
                pieceColors[piece.index], blkW / 2, blkH / 2);
      v.backDirty.bits |= 3 << row;
      row += 2;
    }
  }
  release_screen();
  v.frontDirty.dirtyNext = false;
}

    void drawScore(LJView v) {
  long gameTime = v.field.gameTime;
  int seconds = (int)gameTime / 60;
  int minutes = seconds / 60;
  int baseX = v.plat.baseX;
  int tpm = -1;
  int spawnLeft = v.plat.skin.blkW * LJ_SPAWN_X + baseX;
  int pfRight = v.plat.skin.blkW * LJ_PF_WID + baseX;

  if (withPics >= 0) {
    if (v.field.nPieces != withPics) {
      saveScreen(v.field.nPieces);
    }
    withPics = v.field.nPieces;
  }

  if (v.nLockTimes >= 2 ) {
    int time = v.lockTime[0] - v.lockTime[v.nLockTimes - 1];
    if (time > 0) {
      tpm = 3600 * (v.nLockTimes - 1) / time;
    }
  }

  if (v.frontDirty.dirtyScore) {
    switch (v.plat.skin.nextPos) {
    case TOP:
      blit(v.plat.skin.bg, screen,
           pfRight, 72,
           pfRight, 72,
           112, 136);

      textout_ex(screen, aver32, "Lines:", pfRight, 72, fgColor, -1);
      textprintf_right_ex(screen, aver32, pfRight + 104, 102, fgColor, -1,
                          ""+v.field.lines);
      textout_ex(screen, aver32, "Score:", pfRight, 142, fgColor, -1);
      textprintf_right_ex(screen, aver32, pfRight + 104, 172, fgColor, -1,
                          ""+v.field.score);
      textout_ex(screen, aver32, "Level:", pfRight, 212, fgColor, -1);
      textprintf_right_ex(screen, aver32, pfRight + 104, 242, fgColor, -1,
                          ""+v.field.speedState.level);
      break;

    default:
      blit(v.plat.skin.bg, screen, spawnLeft, 12, spawnLeft, 12, 288, 60);
      textprintf_right_ex(screen, aver32, spawnLeft + 288, 12, fgColor, -1,
                          "Lv. "+v.field.speedState.level);
      textprintf_ex(screen, aver32, spawnLeft, 12, fgColor, -1,
                    "Lines: "+v.field.lines);
      textprintf_ex(screen, aver32, spawnLeft, 42, fgColor, -1,
                    "Score: "+v.field.score);
      break;
    }
  }

  /* If speed is defined, and there is room to draw it, draw it. */
  if (tpm > 0 && v.nextPieces <= 3) {
    blit(v.plat.skin.bg, screen,
           pfRight, 282,
           pfRight, 282,
           104, 60);
    textout_ex(screen, aver32, "Speed:", pfRight, 282, fgColor, -1);
    textprintf_right_ex(screen, aver32, pfRight + 104, 312, fgColor, -1,
                        ""+tpm);
  }

  if (v.frontDirty.dirtyNext) {

    // Erase gimmick
    blit(v.plat.skin.bg, screen,
         baseX + (LJ_PF_WID / 2) * v.plat.skin.blkW,
         v.plat.skin.baseY + 8,
         baseX + (LJ_PF_WID / 2) * v.plat.skin.blkW,
         v.plat.skin.baseY + 8,
         v.plat.skin.blkW, 30);
    // Draw gimmick
    if (v.field.gimmick != LJGM.NONE /*&& v.field.gimmick < LJGM.values().length*/) {
      final String gimmickName = ""; /*ljGetFourCCName(gimmickNames[v.field.gimmick]);*/
      textout_centre_ex(screen, aver32,
                        gimmickName != null ? gimmickName : "Bad gimmick ID",
                        baseX + (LJ_PF_WID / 2) * v.plat.skin.blkW,
                        v.plat.skin.baseY + 8,
                        fgColor, -1);
    }
  }
  drawNextPieces(v);

  blit(v.plat.skin.bg, screen, pfRight, 42, pfRight, 42, 96, 30);
//#if 0
  // Use this for DEBUG inspection into a variable
//  textprintf_right_ex(screen, aver16, pfRight + 96, 8, fgColor, -1,
//                      "%d", v.field.bpmCounter);
//#endif
  textprintf_right_ex(screen, aver32, pfRight + 96, 42, fgColor, -1,
                      ("" + minutes + ":" + (seconds - 60 * minutes)));

}

    void blitField(LJView v) {
  int blkH = v.plat.skin.blkH;
  int rowY = v.plat.skin.baseY
             - v.plat.skin.pfElev
             - blkH * v.field.ceiling;
  int x = v.plat.skin.blkW * v.field.leftWall;
  int w = v.plat.skin.blkW * (v.field.rightWall - v.field.leftWall);

  // Copy each dirty row
  for (int y = v.field.ceiling - 1; y >= 0; --y) {
    if (v.frontDirty.get(y)) {
      int top = (LJ_PF_VIS_HT - y - 1) * blkH;
      int ht = 0;

      // Find the height of the contiguous rows to blit
      do {
        ht += blkH;
        --y;
      } while ((y >= 0)
               && (v.frontDirty.get(y)));
      blit(v.plat.back, screen,
           x, top,
           x + v.plat.baseX, rowY,
           w, ht);
      rowY += ht;
    }
    rowY += blkH;
  }

  v.frontDirty.bits &= (~0) << LJ_PF_HT;
}

void saveScreen(int n) {
  Image b = create_bitmap(SCREEN_W, SCREEN_H);
  if (b != null) {
    blit(screen, b, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
    if (n < 0) {
      save_bitmap("ljsnap.bmp", b);
    } else {
      String filename = "pics/lj" + n + ".bmp";
      save_bitmap(filename, b);
    }
    destroy_bitmap(b);
  }
}

    private void save_bitmap(String filename, Image b) {

    }

    public static final int PRESETS_PER_COL = 6;
public static final int N_NONPRESETS = 2;
public static final int PRESETS_TOP = 140;
public static final int PRESET_COL_WIDTH = 250;
public static final int PRESET_ROW_HT = 40;

static final String[] nonPresetNames = {
  "Full Custom", "Back"
};

static void getPresetDrawRow(int preset, boolean hilite) {
  int ht = text_height(aver32);
  String txt = ""; /*preset < nLoadedPresets
                    ? loadedPresets[preset].name
                    : nonPresetNames[preset - nLoadedPresets];*/
  if (txt == null) {
    txt = "Bad";
  }
  int wid = text_length(aver32, txt);

  int buttonCol = preset / PRESETS_PER_COL;
  int buttonX = 20 + buttonCol * PRESET_COL_WIDTH;
  int buttonY = PRESETS_TOP
                + PRESET_ROW_HT * (preset - buttonCol * PRESETS_PER_COL);
  int buttonWidth = wid + 16;

  rectfill(screen,
           buttonX, buttonY,
           buttonX + buttonWidth - 1, buttonY + PRESET_ROW_HT - 1,
           hilite ? hiliteColor : bgColor);
  if (hilite) {
    rect(screen,
         buttonX, buttonY,
         buttonX + buttonWidth - 1, buttonY + PRESET_ROW_HT - 1,
         fgColor);
  }
  textout_ex(screen, aver32, txt,
             8 + buttonX,
             20 + buttonY - ht / 2,
             fgColor, -1);
}

    private static void rect(BufferedImage screen, int buttonX, int buttonY, int i, int i1, int fgColor) {

    }

    private static int text_length(CoreFont aver32, String txt) {
        return aver32.getStringWidth(txt);
    }

    private static int text_height(CoreFont aver32) {
        return aver32.getHeight();
    }

/*
    int getPreset(int lastPreset) {
  LJBits lastKeys = new LJBits(true);
  redrawWholeScreen = true;

  clear_keybuf();
  if (lastPreset < 0) {
    lastPreset += nLoadedPresets + N_NONPRESETS;
  }

  for(int done = 0; done == 0; ) {
    if (redrawWholeScreen) {
      redrawWholeScreen = false;
      acquire_screen();
      clear_to_color(screen, bgColor);
      textout_ex(screen, aver32, "LOCKJAW > Play", 16, 32, fgColor, -1);
      textout_ex(screen, aver32, "Select a scenario:", 16, 90, fgColor, -1);

      for (int preset = 0;
            preset < nLoadedPresets + N_NONPRESETS;
            ++preset) {
        getPresetDrawRow(preset, preset == lastPreset);
      }
      textout_ex(screen, aver16, "Arrows: move; Rotate Right: start",
                 16, PRESETS_TOP + 40 * (PRESETS_PER_COL + 1), fgColor, -1);
      textout_ex(screen, aver16, "Coming soon: an editor for these!",
                 16, PRESETS_TOP + 40 * (PRESETS_PER_COL + 1) + 20, fgColor, -1);
      release_screen();
    }
    while (keypressed()) {
      int scancode = 0;
      ureadkey(scancode);
      if (scancode == KEY_PRTSCR) {
        saveScreen(-1);
      }
    }

    int preset = lastPreset;
    LJBits keys = menuReadPad();
    LJBits newKeys = new LJBits(keys.bits & ~lastKeys.bits);

    if ((newKeys & VKEY_ROTL) || wantsClose) {
      preset = nLoadedPresets + N_NONPRESETS - 1;
      ezPlaySample("rotate_wav", 128);
    }
    if ((newKeys & VKEY_ROTR) || wantsClose) {
      done = 1;
      ezPlaySample("line_wav", 128);
    }

    if (newKeys & VKEY_UP) {
      if (preset <= 0) {
        preset = nLoadedPresets + N_NONPRESETS - 1;
      } else {
        --preset;
      }
      ezPlaySample("shift_wav", 128);
    }
    if (newKeys & VKEY_DOWN) {
      ++preset;
      if (preset >= nLoadedPresets + N_NONPRESETS) {
        preset = 0;
      }
      ezPlaySample("shift_wav", 128);
    }

    if (newKeys & VKEY_LEFT) {
      if (preset < PRESETS_PER_COL) {
        preset += (((nLoadedPresets + N_NONPRESETS)
                     / PRESETS_PER_COL)
                    )
                   * PRESETS_PER_COL;
        if (preset >= nLoadedPresets + N_NONPRESETS) {
          preset -= PRESETS_PER_COL;
        }
      } else {
        preset -= PRESETS_PER_COL;
      }
      ezPlaySample("shift_wav", 128);
    }
    if (newKeys & VKEY_RIGHT) {
      preset += PRESETS_PER_COL;
      if (preset >= nLoadedPresets + N_NONPRESETS) {
        preset %= PRESETS_PER_COL;
      }
      ezPlaySample("shift_wav", 128);
    }

    if (preset != lastPreset) {
      vsync();
      acquire_screen();
      getPresetDrawRow(lastPreset, false);
      getPresetDrawRow(preset, true);
      release_screen();
      lastPreset = preset;
    } else {
      rest(30);
    }
    lastKeys = keys;
  }

  // Wrap the nonpresets into the negative numbers.
  if (lastPreset >= nLoadedPresets) {
    lastPreset -= (nLoadedPresets + N_NONPRESETS);
  }

  return lastPreset;
}
*/

    private void clear_keybuf() {

    }

    /** Pauses the game, returning true if the player wants to quit. */
boolean pauseGame(LJPCView v) {
  int escCount = 0;
  boolean quit = false;
  int escHold = curTime;
  redrawWholeScreen = true;

  LJMusic_pause(v.skin.bgm, 1);
  while (escCount < 2 && !quit) {
    LJMusic_poll(v.skin.bgm);
    if (redrawWholeScreen) {
      redrawWholeScreen = false;
      acquire_screen();
      clear_to_color(screen, pfBgColor);
      textout_centre_ex(screen, aver32, "LOCKJAW: GAME PAUSED",
                        SCREEN_W / 2, 200, pfFgColor, -1);
      textout_centre_ex(screen, aver32, "Press Esc to continue",
                        SCREEN_W / 2, 250, pfFgColor, -1);
      textout_centre_ex(screen, aver32, "or hold Esc to quit",
                        SCREEN_W / 2, 300, pfFgColor, -1);
      release_screen();
    }

    if (false/*key[KEY_ESC]*/) {
      if (escHold == 0) {
        escHold = curTime;
      }
      if (curTime - escHold >= 60) {
        quit = true;
      }
    } else {
      if (escHold != 0) {
        ++escCount;
      }
      escHold = 0;
    }
    rest(30);
    if (wantsClose) {
      quit = true;
    }

  }
  LJMusic_pause(v.skin.bgm, 0);
  redrawWholeScreen = true;
  clear_keybuf();
  return quit;
}

    void pcInit(LJView v, LJPrefs prefs) {
  v.plat.b2bcd1 = 0;
  v.plat.b2bcd2 = 0;
  v.plat.baseX = v.plat.skin.baseX;

  // If the player has chosen to use more next pieces than the
  // next piece position can handle, set the number of
  // next pieces for correctness of debrief().
  if (v.nextPieces > 3 && v.plat.skin.nextPos == LJNEXT.TOP) {
    v.nextPieces = 3;
  }
}

/**
 * Redraws everything on the screen.
 * Called when needs redraw.
 */
void playRedrawScreen(LJView v) {
  blit(v.plat.skin.bg, screen,
       0, 0,
       0, 0,
       SCREEN_W, SCREEN_H);
  v.frontDirty.fill();
}

//#if 0
void startingAniWantsSkip(LJView v) {
  LJInput unusedIn = new LJInput();
  addKeysToInput(unusedIn, readPad(), v.field, v.control);
}

    protected void addKeysToInput(LJInput unusedIn, boolean bool, LJField field, LJControl control) {
    }

    private boolean readPad() {
        return true;
    }
    protected boolean readPad(int player) {
        return true;
    }
//#endif

//void playSampleForTetromino(LJP piece);

void restPollingMusic(int nFrames, LJMusic bgm) {
  nFrames += curTime;
  while (curTime - nFrames < 0) {
    if (bgm != null) {
      LJMusic_poll(bgm);
    }
  }
}

void startingAnimation(LJView v) {
  int readyGoX = v.plat.baseX + v.plat.skin.blkW * LJ_PF_WID / 2;
  int readyGoY = v.plat.skin.baseY
                 - v.plat.skin.pfElev
                 - v.plat.skin.blkH
                   * v.field.ceiling * 3 / 5;

  clear_keybuf();
  v.backDirty.clear();

  acquire_screen();
  playRedrawScreen(v);
  blitField(v);
  textout_centre_ex(screen, aver32, "Ready",
                    readyGoX, readyGoY, pfFgColor, -1);
  release_screen();

  ezPlaySample("ready_wav", 128);
  restPollingMusic(36, bgmReadyGo ? v.plat.skin.bgm : null);
  v.frontDirty.fill();

  if (!wantsClose) {
    acquire_screen();
    blitField(v);
    textout_centre_ex(screen, aver32, "GO!",
                    readyGoX, readyGoY, pfFgColor, -1);
    drawScore(v);
    release_screen();

    ezPlaySample("go_wav", 128);
    v.frontDirty.fill();
    restPollingMusic(12, bgmReadyGo ? v.plat.skin.bgm : null);
  }
  if (!wantsClose) {
    playSampleForTetromino(v.field.curPiece[1]);
    restPollingMusic(24, bgmReadyGo ? v.plat.skin.bgm : null);
  }
}


static void gameOverAnimation(final LJPCView v, final LJField p, boolean won) {
  int ceiling = p.ceiling;
  int left = v.baseX + p.leftWall * v.skin.blkW;
  int right = v.baseX + p.rightWall * v.skin.blkW - 1;

  ezPlaySample("sectionup_wav", 0);
  if (!won) {
    ezPlaySample("gameover_wav", 256);
  } else {
    ezPlaySample("win_wav", 256);
  }

  for (int t = ceiling + v.skin.blkH - 2; t >= 0; --t) {
    acquire_screen();
    for (int row = ceiling - 1; row >= 0; --row) {
      int ysub = t - row;

      if (ysub >= 0 && ysub < v.skin.blkH) {
        int y = v.skin.baseY - v.skin.pfElev
                - row * v.skin.blkH - ysub - 1;
        hline(screen, left, y, right, pfBgColor);
      }
    }
    release_screen();
    vsync();
    if (wantsClose) {
      t = 0;
    }
  }
}

public static final int MENU_COPR_NOTICE_LINES = 4;
final String menuCoprNotice[] = {
  "Copr. 2006-2007 Damian Yerrick",
  "Not sponsored or endorsed by The Tetris Company.",
  "LOCKJAW comes with ABSOLUTELY NO WARRANTY.  This is free software, and you are",
  "welcome to redistribute it under certain conditions as described in GPL.txt."
};

static Image buildTitleScreen() {
  BufferedImage back = create_system_bitmap(SCREEN_W, SCREEN_H);
  if (back == null) {
    return null;
  }

  // Gradient from (0, 0, 0) to (0, 0, 153)
  for (int y = 0; y < 192; ++y) {
    for (int x = -((y * 13) & 0x1F);
          x < SCREEN_W;
          x += 32) {
      int colValue = y + ((rand() & 0x7000) >> 12);
      int c = makecol(0, 0, 153 * colValue / 192);
      hline(back, x, y, x + 31, c);
    }
  }

  // Gradient from (102, 51, 0) to (204, 102, 0)
  for (int y = 192; y < 384; ++y) {
    for (int x = -((y * 13) & 0x1F);
          x < SCREEN_W;
          x += 32) {
      int colValue = y + ((rand() & 0x7800) >> 11);
      int c = makecol(102 * colValue / 192, 51 * colValue / 192, 0);
      hline(back, x, y, x + 31, c);
    }
  }

  // Gradient from (204, 102, 0) to (255, 128, 0)
  for (int y = 384; y < SCREEN_H; ++y) {
    for (int x = -((y * 13) & 0x1F);
          x < SCREEN_W;
          x += 32) {
      int colValue = y - 400 + ((rand() & 0x7C00) >> 10);
      if (colValue > 600 - 384) {
        colValue = 600 - 384;
      }
      int c = makecol(204 + 50 * colValue / (600 - 384),
                      102 + 25 * colValue / (600 - 384),
                      0);
      hline(back, x, y, x + 31, c);
    }
  }

  CoreImage logo = CoreImage.load("arttitle_bmp");
  if (logo != null) {
    draw_sprite(back, logo,
                (SCREEN_W - logo.getWidth()) / 2, (384 - logo.getHeight()) / 2);
    //unselect_palette();
  }

  textout_centre_ex(back, aver32, "Arrows: change; Enter: choose",
                    SCREEN_W / 2, 440,
                    0, -1);

  textout_centre_ex(back, aver32, "LOCKJAW: The Reference "+ LJ_VERSION,
                    SCREEN_W / 2, SCREEN_H - 40,
                    0, -1);

  return back;
}

    enum TITLE {
        TITLE_EXIT("Exit"),
        TITLE_PLAY("Play"),
        TITLE_REPLAY("Replay"),
        TITLE_OPTIONS("Skin..."),
        TITLE_SKIN("Options..."),
        TITLE_KEYS("Game Keys..."),;
        private String s;
        TITLE(String s) {this.s = s;}
    }

    static final List<TITLE> titleActions = new ArrayList<TITLE>(EnumSet.allOf(TITLE.class));
/*
    static final Map<TITLE, String> titleActions = new EnumMap<TITLE, String>(TITLE.class);
    static {
        titleActions.put(TITLE.TITLE_EXIT, "Exit");
        titleActions.put(TITLE.TITLE_PLAY, "Play");
        titleActions.put(TITLE.TITLE_REPLAY, "Replay");
        titleActions.put(TITLE.TITLE_SKIN, "Skin...");
        titleActions.put(TITLE.TITLE_OPTIONS, "Options...");
        titleActions.put(TITLE.TITLE_KEYS, "Game Keys...");
    }
*/

    /**
     * 0: Exit
     * 1: Play
     * 2
     */
    TITLE title() {

  // don't even draw if the player is trying to close the window
  if (wantsClose) {
    return TITLE.TITLE_EXIT;
  }

  Image back = buildTitleScreen();
  LJBits lastKeys = new LJBits();
        lastKeys.fill();
  boolean redraw = true;
  int choice = 1;

  if (back == null) {
    alert("Not enough memory to display the title screen." +
          "(If you don't even have RAM for a title screen," +
          "then what do you have RAM for?)" +
          "Exit", 0, 13, 0);
    return TITLE.TITLE_EXIT;
  }

  redrawWholeScreen = true;

        boolean done = false;
        while (!done) {
            if (redrawWholeScreen) {
                redrawWholeScreen = false;
                blit(back, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
                redraw = true;
            }
            if (redraw) {
                int dehilite = makecol(221, 153, 85);
                int white = makecol(255, 255, 255);
                redraw = false;
                vsync();
                blit(back, screen, 0, 400, 0, 400, SCREEN_W, 30);
                textout_centre_ex(screen, aver32,
                        titleActions.get(choice).s,
                        SCREEN_W / 2, 400, white, -1);
                textout_centre_ex(screen, aver32,
                        titleActions.get(choice == 0 ? titleActions.size() - 1 : choice - 1).s,
                        SCREEN_W / 2 - 160, 400, dehilite, -1);
                textout_centre_ex(screen, aver32,
                        titleActions.get(choice == titleActions.size() - 1 ? 0 : choice + 1).s,
                        SCREEN_W / 2 + 160, 400, dehilite, -1);
            }

/*
            while (keypressed()) {
                int scancode = 0;
                ureadkey(scancode);
                if (scancode == KEY_PRTSCR) {
                    saveScreen(-1);
                }
            }
*/

            LJBits keys = menuReadPad();
            LJBits newKeys = new LJBits(keys.bits & ~lastKeys.bits);

/*
            if (newKeys & VKEY.LEFT) {
                --choice;
                redraw = true;
                ezPlaySample("shift_wav", 128);
            }
            if (newKeys & VKEY.RIGHT) {
                ++choice;
                redraw = true;
                ezPlaySample("shift_wav", 128);
            }
            if (newKeys & VKEY.ROTL) {
                choice = 0;
                redraw = true;
                ezPlaySample("rotate_wav", 128);
            }
            if (newKeys & VKEY.ROTR) {
                done = true;
                ezPlaySample("line_wav", 128);
            }
*/
            if (choice < 0) {
                choice += titleActions.size();
            }
            if (choice >= titleActions.size()) {
                choice -= titleActions.size();
            }

            lastKeys = keys;

            if (!redraw) {
                rest(30);
            }
            if (wantsClose) {
                done = true;
                choice = 0;
            }
        }
        destroy_bitmap(back);
        return titleActions.get(choice);
    }

    void setupWindow() {
        set_window_title("LOCKJAW");
        bgColor = makecol(255, 255, 255);
        pfFgColor = bgColor;
        hiliteColor = makecol(255, 255, 204);

        refreshRate = get_refresh_rate();
    }

    void drawCoprNotice() {
        acquire_screen();
        clear_to_color(screen, pfBgColor);
        for (int i = 0; i < MENU_COPR_NOTICE_LINES; ++i) {
            textout_ex(screen, font, menuCoprNotice[i],
                    16, 580 + 12 * (i - MENU_COPR_NOTICE_LINES),
                    pfFgColor, -1);
        }
        release_screen();
    }

    /**
     * Destroys all system bitmaps that a given view owns.
     * Useful before changing screen mode.
     */
    void destroySystemBitmaps(LJPCView plat) {
        if (plat.back != null) {
            destroy_bitmap(plat.back);
            plat.back = null;
        }
        if (plat.skin.connBlocks != null) {
            destroy_bitmap(plat.skin.connBlocks);
            plat.skin.connBlocks = null;
        }
    }

    boolean openWindow(boolean windowed)
{
/*
  int depth = desktop_color_depth();
  int card = windowed ? GFX_AUTODETECT_WINDOWED : GFX_AUTODETECT_FULLSCREEN;

  */
/* Reference implementation for Allegro is not compatible with
       indexed color. */
/*
  if (depth < 15) {
    depth = 16;
  }

  // Full screen procedure
  set_color_depth(depth);
  if (set_gfx_mode(card, skinW, skinH, 0, 0) == 0) {
    setupWindow();
    return true;
  }

  // Windows can't tell 16 bit from 15 bit. If desktop color depth is reported as 16, try 15 too.
  if (depth == 16) {
    set_color_depth(15);
    if (set_gfx_mode(card, skinW, skinH, 0, 0) == 0) {
      setupWindow();
      return true;
    }
  }

  return false;
*/
    setupWindow();
    return true;
}

static Image loadConnections(String filename, int blkW, int blkH) {
  Image src = load_bitmap(filename, null);

  if (src == null) {
    return null;
  }
  Image dst = create_system_bitmap(blkW*16, blkH*16);
  if (dst == null) {
    destroy_bitmap(src);
    return null;
  }
  acquire_bitmap(dst);
  for (int col = 0; col < 16; ++col) {
    int srcXBase = (col & 0x03) * blkW * 2;
    int srcYBase = (col >> 2) * blkH * 2;
    int dstYBase = col * blkH;
    for (int conn = 0; conn < 16; ++conn) {
      int dstXBase = conn * blkW;
      int topSegY = (conn & CONNECT_U) != 0 ? blkH : 0;
      int botSegY = (conn & CONNECT_D) != 0 ? blkH / 2 : 3 * blkH / 2;
      int leftSegX = (conn & CONNECT_L) != 0 ? blkW : 0;
      int rightSegX = (conn & CONNECT_R) != 0 ? blkW/2 : 3*blkW/2;
      blit(src, dst,
           srcXBase + leftSegX, srcYBase + topSegY,
           dstXBase + 0, dstYBase + 0,
           blkW/2, blkH/2);
      blit(src, dst,
           srcXBase + rightSegX, srcYBase + topSegY,
           dstXBase + blkW/2, dstYBase + 0,
           blkW/2, blkH/2);
      blit(src, dst,
           srcXBase + leftSegX, srcYBase + botSegY,
           dstXBase + 0, dstYBase + blkH/2,
           blkW/2, blkH/2);
      blit(src, dst,
           srcXBase + rightSegX, srcYBase + botSegY,
           dstXBase + blkW/2, dstYBase + blkH/2,
           blkW/2, blkH/2);
    }
  }
  release_bitmap(dst);
  destroy_bitmap(src);
  return dst;
}


static void closeWindow() {
//  set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
}

void mainCleanup(LJPCView v) {
  if (v.back != null) {
    destroy_bitmap(v.back);
  }
  if (v.skin.bg != null) {
    destroy_bitmap(v.skin.bg);
  }
  if (v.skin.blocks != null) {
    destroy_bitmap(v.skin.blocks);
  }
  if (v.skin.connBlocks != null) {
    destroy_bitmap(v.skin.connBlocks);
  }
  LJMusic_delete(v.skin.bgm);
  if (withSound) {
    remove_sound();
  }
  closeWindow();
}

    private void remove_sound() {
    }

    /**
 * Resets all skin settings to their initial values
 * so that skins can override them.
 */
static void loadSkinDefaults(LJView v) {
  ljblocksSRSName = "ljblocks.bmp";
  ljblocksSegaName = "ljblocks-sega.bmp";
  ljconnSRSName = "ljconn.bmp";
  ljconnSegaName = "ljconn-sega.bmp";
  ljbgName = "ljbg.jpg";
  menubgName = "menubg.jpg";
  bgmName = "bgm.s3m";
  bgmRhythmName = "bgm-rhythm.s3m";
  bgmLoopPoint = 0;
  bgmReadyGo = false;
  bgmVolume = 128;
  bgColor = makecol(255, 255, 255);
  fgColor = makecol(0, 0, 0);
  hiliteColor = makecol(255, 255, 204);
  pfBgColor = makecol(0, 0, 0);
  pfFgColor = makecol(255, 255, 255);
  v.plat.skin.blkW = 24;
  v.plat.skin.blkH = 24;
  v.plat.skin.transparentPF = 0;
  v.plat.skin.shiftScale    = false;
  v.plat.skin.baseX       = 0;
  v.plat.skin.nextPos     = LJNEXT.NONE;
}

/**
 * Converts a single hexadecimal digit to its value.
 * @param in USASCII/Unicode value of hex digit character
 * @return value
*/
int hexDigitValue(char in) {
  if (in >= '0' && in <= '9') {
    return in - '0';
  } else if (in >= 'A' && in <= 'F') {
    return in - 'A' + 10;
  } else if (in >= 'a' && in <= 'f') {
    return in - 'a' + 10;
  } else {
    return -1;
  }
}

int translateComponent(String in, int nDigits) {
  String here = in;
  int hi = hexDigitValue(here.charAt(0));
  int lo = nDigits > 3 ? hexDigitValue(here.charAt(1)) : hi;
  in = here;
  if (hi >= 0 && lo >= 0) {
    return hi * 16 + lo;
  } else {
    return -1;
  }
}

/**
 * Interprets a hexadecimal color specifier.
 * @param in hexadecimal color specifier, in #ABC or #AABBCC format
 * @return Allegro device-dependent color, in makecol() format
 */
int translateColor(String in) {
  int nDigits = 0;

  // Verify and skip #
  if (!in.startsWith("#")) {
    return -1;
  }

  // Determine whether we have a 3-digit color or a 6-digit color
  for (int i = 1;
       hexDigitValue(in.charAt(i)) >= 0;
       ++i) {
    ++nDigits;
  }
  if (nDigits != 3 && nDigits != 6) {
    return -1;
  }

  int red = translateComponent(in, nDigits);
  int green = translateComponent(in, nDigits);
  int blue = translateComponent(in, nDigits);
  if (red >= 0 && green >= 0 && blue >= 0) {
    return makecol(red, green, blue);
  } else {
    return -1;
  }
};

/**
 * Loads skin parameters from the file.
 * @param skinName Name of skin ini file
 */
int loadSkinFile(LJView v, String skinName) {

  // Don't use ljfopen here because lj.ini specifies the absolute skin file
    InputStream is = Assets.getAsStream(skinName);
//  File fp = fopen(skinName, "rt");
  String key, var, val, input_buf;
    if(is == null) return -1;

    Properties p = new Properties();
    try {
        p.load(is);
    } catch (IOException e) {
        e.printStackTrace();
    }

  loadSkinDefaults(v);

    int rval;

    if((val =p.getProperty ("ljblocksSRS")) != null) {
      ljblocksSRSName = val;
    }
    if((val =p.getProperty ("ljblocksSega")) != null) {
      ljblocksSegaName = val;
    }
    if((val =p.getProperty ("ljconnSRS")) != null) {
      ljconnSRSName = val;
    }
    if((val =p.getProperty ("ljconnSega")) != null) {
      ljconnSegaName = val;
    }
    if((val =p.getProperty ("bgm")) != null) {
      bgmName = val;
    }
    if((val =p.getProperty ("bgmRhythm")) != null) {
      bgmRhythmName = val;
    }
    if((val =p.getProperty ("ljbg")) != null) {
      ljbgName = val;
    }
    if((val =p.getProperty ("bgmLoopPoint")) != null) {
      long c = Integer.parseInt(val);
      if (c >= 0) {
        bgmLoopPoint = c;
      }
    }
    if((val =p.getProperty ("bgmVolume")) != null) {
      int c = Integer.parseInt(val);
      if (c >= 0) {
        bgmVolume = c;
      }
    }
    if((val =p.getProperty ("bgmReadyGo")) != null) {
      boolean c = Boolean.parseBoolean(val);
      if (c) {
        bgmReadyGo = c;
      }
    }
    if((val =p.getProperty ("pfbgcolor")) != null) {
      int c = translateColor(val);
      if (c >= 0) {
        pfBgColor = c;
      }
    }
    if((val =p.getProperty ("pfcolor")) != null) {
      int c = translateColor(val);
      if (c >= 0) {
        pfFgColor = c;
      }
    }
    if((val =p.getProperty ("bgcolor")) != null) {
      int c = translateColor(val);
      if (c >= 0) {
        bgColor = c;
      }
    }
    if((val =p.getProperty ("color")) != null) {
      int c = translateColor(val);
      if (c >= 0) {
        fgColor = c;
      }
    }
    if((val =p.getProperty ("hilitecolor")) != null) {
      int c = translateColor(val);
      if (c >= 0) {
        hiliteColor = c;
      }
    }
    if((val =p.getProperty ("blkW")) != null) {
      int c = Integer.parseInt(val);
      if (c >= 0) {
        v.plat.skin.blkW = c;
      }
    }
    if((val =p.getProperty ("blkH")) != null) {
      int c = Integer.parseInt(val);
      if (c >= 0) {
        v.plat.skin.blkH = c;
      }
    }
    if((val =p.getProperty ("transparentPF")) != null) {
      int c = Integer.parseInt(val);
      if (c >= 0) {
        v.plat.skin.transparentPF = c;
      }
    }
    if((val = p.getProperty("shiftScale")) != null) {
      boolean c = Boolean.parseBoolean(val);
      if (c) {
        v.plat.skin.shiftScale = c;
      }
    }
    if((val = p.getProperty("baseX")) != null) {
      int c = Integer.parseInt(val);
      if (c >= 0) {
        v.plat.skin.baseX = c;
      }
    }
    if((val = p.getProperty("nextPos")) != null) {
        v.plat.skin.nextPos = LJNEXT.valueOf(val);
    }
    if((val = p.getProperty("wndW")) != null) {
      int c = Integer.parseInt(val);
      if (c >= 0) {
        skinW = c;
      }
    }
    if((val = p.getProperty("wndH")) != null) {
      int c = Integer.parseInt(val);
      if (c >= 0) {
        skinH = c;
      }
    }
//  ljpathSetSkinFolder(skinName);
  return 0;
}

static void drawProgressSegment(int min, int max) {
  min = min * SCREEN_W / 100;
  max = max * SCREEN_W / 100;
  int blue = makecol(0, 0, 255);
  rectfill(screen, min, SCREEN_H - 8, max - 1, SCREEN_H - 5, blue);
  int orange = makecol(255, 128, 0);
  rectfill(screen, min, SCREEN_H - 4, max - 1, SCREEN_H - 1, orange);
}

int loadSkin(LJView v, final String skinName) {
  Image bmp;
  final LJRotSystem rs = WK.rotSystems.get(v.field.rotationSystem);
  int colorScheme = rs.colorScheme;

  rectfill(screen, 0, 592, 799, 599, 0);

  loadSkinFile(v, skinName);
  drawProgressSegment(0, 20);

  if (v.plat.back != null) {
    destroy_bitmap(v.plat.back);
  }
  v.plat.back = create_system_bitmap(v.plat.skin.blkW * LJ_PF_WID,
                                        v.plat.skin.blkH * LJ_PF_VIS_HT);
  if(v.plat.back == null) {
    allegro_message("Could not create back buffer.\n");
    return 0;
  }

  // Load background image
  bmp = load_bitmap(ljbgName, null);
  if (v.plat.skin.bg != null) {
    destroy_bitmap(v.plat.skin.bg);
  }
  if (bmp != null) {

    // If the image size doesn't match the window size, resize it
    if (bmp.getWidth(null) != SCREEN_W || bmp.getHeight(null) != SCREEN_H) {
      Image resized = create_bitmap(SCREEN_W, SCREEN_H);

      if (resized != null) {
        stretch_blit(bmp, resized, 0, 0, bmp.getWidth(null), bmp.getHeight(null),
                     0, 0, SCREEN_W, SCREEN_H);
        destroy_bitmap(bmp);
      }
        bmp = resized;
    }
  }
  if(bmp == null) {
    bmp = create_bitmap(SCREEN_W, SCREEN_H);
    if (bmp != null) {
      allegro_message("Background image \"" + ljbgName + "\" not found.\n"+
                      "Using plain background instead.\n");
      clear_to_color(bmp, bgColor);
    } else {
      allegro_message("Background image \""+ljbgName+"\" not found.\n");
      return 0;
    }
  }
  v.plat.skin.bg = bmp;
  drawProgressSegment(20, 40);

  // load block images
  if (v.plat.skin.blocks != null) {
    destroy_bitmap(v.plat.skin.blocks);
  }
  bmp = load_bitmap(ljconnSegaName, null);
  v.plat.skin.blocks = bmp;
  if(v.plat.skin.blocks == null) {
    allegro_message("Background image \""+ljbgName+"\" not found.\n");
    return 0;
  }
  drawProgressSegment(40, 60);

  // load connected block images
  if (v.plat.skin.connBlocks != null) {
    destroy_bitmap(v.plat.skin.connBlocks);
  }
  bmp = loadConnections(ljconnSegaName,
                          v.plat.skin.blkW,
                          v.plat.skin.blkH);

  v.plat.skin.connBlocks = bmp;
  drawProgressSegment(60, 80);

  // load music
  boolean isRhythm = (v.field.speedState.curve == LJSPD.RHYTHM);
    LJMusic_load(v.plat.skin.bgm, bgmName);
  if (!isRhythm) {
    LJMusic_setLoop(v.plat.skin.bgm, (int)bgmLoopPoint);
  }
  drawProgressSegment(80, 100);
  return 1;
}

    void drop_mouse() {
  while (Input.getMouseY() < SCREEN_H - 25) {
//    position_mouse(mouse_x, mouse_y + 20);
    rest(10);
  }
  ezPlaySample("land_wav", 192);
//  position_mouse(Input.getMouseX(), SCREEN_H - 5);
  ezPlaySample("lock_wav", 192);
}

boolean pickReplay() {
  CoreFont oldFont = font;
  font = aver16;
//  install_mouse();
  boolean got = false;//file_select_ex("Choose a demo:", demoFilename, "ljm", sizeof(demoFilename), 600, 400);
  drop_mouse();
//  remove_mouse();
  font = oldFont;
  return got;
}

void badReplay() {
  acquire_screen();
  clear_to_color(screen, bgColor);
  textout_ex(screen, aver32, "The demo", 100, 100, fgColor, -1);
  textout_ex(screen, aver16, demoFilename, 100, 130, fgColor, -1);
  textout_ex(screen, aver32, "could not be played because it was", 100, 150, fgColor, -1);
  textout_ex(screen, aver32, "recorded with a different version", 100, 180, fgColor, -1);
  textout_ex(screen, aver32, "of LOCKJAW software.", 100, 210, fgColor, -1);
  release_screen();

  LJBits lastKeys = new LJBits(true);
  LJBits keys;
    LJBits newKeys = new LJBits();

/*
  do {
    keys = menuReadPad();
    newKeys.bits = keys.bits & ~lastKeys.bits;
    lastKeys = keys;
    rest(30);
  } while (!(newKeys & (VKEY.ROTL | VKEY.ROTR)));
*/
}

    private LJBits menuReadPad() {
        return new LJBits();
    }

    int pickSkin() {
  CoreFont oldFont = font;
  font = aver16;
//  install_mouse();
  boolean got = false;//file_select_ex("Choose a skin:", skinName, "skin", skinName.length(), 600, 400);
  drop_mouse();
//  remove_mouse();
  font = oldFont;
  return got ? 0 : -1;
}

void calcElev(LJView v) {
  int blkH = v.plat.skin.blkH;
  int ceiling = v.field.ceiling;
  int elev = (LJ_PF_VIS_HT - ceiling) * blkH;

  if (elev > 480 - ceiling * blkH) {
    elev = 480 - ceiling * blkH;
  }
  if (elev < 0) {
    elev = 0;
  }
  v.plat.skin.pfElev = elev;
}

//public int main(final int argc, final String[] argv) {
    public void load() {
  String cmdLineDemo = null;
  int lastPreset = -2;  // start out with full custom
  LJPCSkin skin = new LJPCSkin();
    {
    skin.baseY = 552;
    skin.blkW = 24;
    skin.blkH = 24;
  }
  LJField[] p = new LJField[2];

  LJControl[] control = new LJControl[2]; {
    control[0] = new LJControl();
    {
      control[0].replaySrc = null;
      control[0].replayDst = null;
    }
  }
  LJPCView[] platView = new LJPCView[2]; {
    platView[0] = new LJPCView();
    {
      platView[0].skin = skin;
    }
    platView[1] = new LJPCView();{
      platView[1].skin = skin;
    }
  }
  LJView[] mainView = new LJView[2]; {
    mainView[0] = new LJView(platView[0],control[0],p[0]);
    mainView[1] = new LJView(platView[1],control[1],p[1]);
  }

  LJPrefs prefs = new LJPrefs(); {
    prefs.number.put(PCOPTIONS.TRAILS,true);
    prefs.number.put(PCOPTIONS.AUTO_PAUSE,true);
    prefs.number.put(PCOPTIONS.AUTO_RECORD,false);
    prefs.number.put(PCOPTIONS.WINDOWED,true);
  }

//  allegro_init();
//  ljpathInit(argc > 0 ? argv[0] : ".");
//  install_timer();
  initOptions(prefs.number);
  loadOptions(prefs);
  loadSkinFile(mainView[0], skinName);

/*
  if (argc > 1) {
    if (argv[1].startsWith("-")) {
      if ("--help".equals(argv[1])
          || "-h".equals(argv[1])) {
        allegro_message("Usage: lj [DEMOFILE]\n");
        return 0;
      }
    } else {
      cmdLineDemo = argv[1];
    }
  }
*/

  if (!openWindow(prefs.number.get(PCOPTIONS.WINDOWED))) {
    allegro_message("LOCKJAW fatal error: Could not open an " + skinW + "x"+skinH+" pixel " +
            (prefs.number.get(PCOPTIONS.WINDOWED) ? "window": "screen mode") +
            ".\nTrying " +
            (prefs.number.get(PCOPTIONS.WINDOWED) ? "the full screen": "a window") +
            " next time.\n");
    prefs.number.put(PCOPTIONS.WINDOWED,!prefs.number.get(PCOPTIONS.WINDOWED));
    saveOptions(prefs);
    return;// EXIT_FAILURE;
  }
  drawCoprNotice();

/*
  LOCK_FUNCTION(incCurTime);
  LOCK_VARIABLE(curTime);
  install_int_ex(incCurTime, BPM_TO_TIMER(3600));

  jpgalleg_init();

  set_color_conversion(COLORCONV_NONE);
*/

/*
  {
    String path;
    if (ljpathFind_r(path, "lj.dat")) {
      dat = load_datafile(path);
    }
  }
  set_color_conversion(COLORCONV_TOTAL);
*/

/*
  if(dat == null) {
    closeWindow();
    allegro_message("LOCKJAW fatal error: Could not load datafile lj.dat\n");
    return 1;
  }
*/

  {
    aver16 = CoreFont.load("Aver16.bmp");
    aver32 = CoreFont.load("Aver32.bmp");
  }

//  LOCK_FUNCTION(amnesia);
//  LOCK_VARIABLE(redrawWholeScreen);
//
//  // If we can be notified on switching out, take this notification.
//  if (set_display_switch_mode(SWITCH_BACKGROUND) >= 0
//      || set_display_switch_mode(SWITCH_BACKAMNESIA) >= 0) {
//    set_display_switch_callback(SWITCH_OUT, requestPause);
//  }
//  set_display_switch_callback(SWITCH_IN, amnesia);
//
//  install_keyboard();
//  initKeys();

  p[0].seed = CoreSystem.getTimeMillis();

//  reserve_voices(8, 0);
//  set_volume_per_voice(0);
//#ifdef ALLEGRO_WINDOWS
  // Under Windows, use the Allegro mixer because on my machine
  // and probably others, the built-in mixer will replace the very
  // beginning of one sound with the end of the last sound played
  // on that voice.
//  withSound = !install_sound(DIGI_DIRECTAMX(0), MIDI_NONE, null);
//#else
//  withSound = !install_sound(DIGI_AUTODETECT, MIDI_NONE, null);
//#endif

  skin.bgm = LJMusic_new();

//  {
//    String path;
//    if (ljpathFind_r(path, "sound.dat")) {
//      sound_dat = load_datafile(path);
//    }
//  }

  unpackOptions(mainView[0], prefs);
  if (loadSkin(mainView[0], skinName) < 0) {
    mainCleanup(platView[0]);
    return;// EXIT_FAILURE;
  } else {

  }

    if (skin.blocks == null) {
        mainCleanup(platView[0]);
        allegro_message("Blocks image " +
                (p[0].rotationSystem != LJROT.SEGA ? ljblocksSegaName : ljblocksSRSName) +
                " not found.\n");
        return;// 1;
    }

  srand(CoreSystem.getTimeMillis());

  // Wait for copyright notice to be displayed "conspicuously"
  if (curTime < 180) {
    rest(3100 - curTime * 16);
  }


  for (TITLE action = cmdLineDemo != null ? TITLE.TITLE_REPLAY : title();
        action != TITLE.TITLE_EXIT && !wantsClose;
        action = title()) {
    switch (action) {
    case TITLE_PLAY:
      for (int preset = getPreset(lastPreset);
           preset != -1 && !wantsClose;
           preset = getPreset(lastPreset))
      {
        lastPreset = preset;
        unpackOptions(mainView[0], prefs);
        if (preset >= 0) {
          presetStart();
          presetAdd(preset);
          presetFinish(mainView[0]);
        }
      // reload the skin
      if (loadSkin(mainView[0], skinName) < 0) {
        mainCleanup(platView[0]);
        return;// EXIT_FAILURE;
      }

        calcElev(mainView[0]);
        pcInit(mainView[0], prefs);
        wantPause = false;
        platView[0].wantRecord = prefs.number.get(PCOPTIONS.AUTO_RECORD);
        LJMusic_start(skin.bgm,
                      4096,  // mix buffer size
                      bgmVolume);  // volume scale

        final LJView[] players = {mainView[0], mainView[1]};
        play(players, 1);
        LJMusic_stop(skin.bgm);
        if (!wantsClose) {
          gameOverAnimation(platView[0], p[0],
                            control[0].countdown <= 0);
          debrief(mainView[0]);
        }
      }
      break;

    case TITLE_REPLAY:
      {
        if (cmdLineDemo != null) {
          demoFilename = cmdLineDemo;
          cmdLineDemo = null;
        } else if (!pickReplay()) {
          break;
        }

        unpackOptions(mainView[0], prefs);
        calcElev(mainView[0]);
        pcInit(mainView[0], prefs);
        wantPause = false;
        platView[0].wantRecord = false;
        LJMusic_start(skin.bgm,
                      4096,  // mix buffer size
                      bgmVolume);  // volume scale

        final LJView[] players = {mainView[0], mainView[1]};

        p[0].gimmick = LJGM.NONE;  // gimmick must be < 0 to activate replay
        play(players, 1);
        LJMusic_stop(skin.bgm);
        if (p[0].gimmick == LJGM.NONE) {
          badReplay();
        } else {
          if (!wantsClose) {
            gameOverAnimation(platView[0], p[0],
                              control[0].countdown <= 0);
            debrief(mainView[0]);
          }
        }
      }
      break;

    case TITLE_SKIN:
      pickSkin();

      // if resolution changed, reopen the window
      {
        int oldW = skinW;
        int oldH = skinH;
        loadSkinFile(mainView[0], skinName);
        if (skinH != oldH || skinW != oldW) {
          destroySystemBitmaps(platView[0]);
          openWindow(prefs.number.get(PCOPTIONS.WINDOWED));
        }
      }

      // reload the skin
      if (loadSkin(mainView[0], skinName) < 0) {
        mainCleanup(platView[0]);
        return;// EXIT_FAILURE;
      }

      // save options
      saveOptions(prefs);
      break;

    case TITLE_OPTIONS:
      {
        boolean oldWindowed = prefs.number.get(PCOPTIONS.WINDOWED);
        options(mainView[0], prefs.number);
        if (oldWindowed != prefs.number.get(PCOPTIONS.WINDOWED)) {
          destroySystemBitmaps(platView[0]);
          openWindow(prefs.number.get(PCOPTIONS.WINDOWED));
        }
      }
      saveOptions(prefs);

      // reload the skin if the player changed the rotation system
      unpackOptions(mainView[0], prefs);
      if (wantsClose) {
        break;
      }
      if (loadSkin(mainView[0], skinName) < 0) {
        mainCleanup(platView[0]);
        return;// EXIT_FAILURE;
      }
      break;

    case TITLE_KEYS:
      configureKeys();
      break;

    }
  }

  mainCleanup(platView[0]);
  return ;//0;
}

    private int getPreset(int lastPreset) {
        return 0;
    }

    private void loadOptions(LJPrefs prefs) {

    }

    private void presetFinish(LJView ljView) {

    }

    private void presetAdd(int preset) {

    }

    private void presetStart() {

    }

    private void debrief(LJView ljView) {

    }

    private void configureKeys() {

    }

    private void options(LJView ljView, Map<PCOPTIONS, Boolean> number) {

    }

    private void initOptions(Map<PCOPTIONS, Boolean> number) {

    }

    private void unpackOptions(LJView ljView, LJPrefs prefs) {
    }

    private void saveOptions(LJPrefs prefs) {
    }




    void play(final LJView v[], int nPlayers) {
      boolean canceled = false;

      for (int player = 0; player < nPlayers; ++player) {
        final LJField p = v[player].field;
        final LJControl ctl = v[player].control;

        ctl.countdown = 6;
        ctl.presses = 0;

/* Load replay if needed */

/*
//#if WITH_REPLAY
    if (p.gimmick == LJGM.NONE) {
      ctl.replaySrc = openReplay(demoFilename, p);
      if (ctl.replaySrc == null) {
        return;
      }
      v[player].backDirty.bits = ~0;
      playRedrawScreen(v[player]);
    } else {
      ctl.replaySrc = null;
    }
//#endif
*/

      }
/*
      if (v[0].control.replaySrc == null) {
        for (int player = 0; player < nPlayers; ++player) {
          final LJField p = v[player].field;
          Lockjaw.newGame(p);
          Gimmicks.initGimmicks(p);
          v[player].nLockTimes = 0;
          v[player].hideNext = false;
          updField(v[player], new LJBits(true));
        }
        startingAnimation(v[0]);
        for (int player = 0; player < nPlayers; ++player) {
          v[player].control.lastKeys.clear();
          v[player].control.repressKeys.clear();
          blitField(v[player]);
        }
      }
*/

      int lastTime = getTime();

      while(v[0].field.state != LJState.GAMEOVER && !canceled) {
        if (getTime() == lastTime) {
          yieldCPU();
        }
        canceled |= ljHandleConsoleButtons(v[0]);
        while (getTime() - lastTime > 0) {
          for (int player = 0; player < nPlayers; ++player) {
            final LJField p = v[player].field;
            final LJControl ctl = v[player].control;
            LJInput in = new LJInput();
            int curTime = getTime();

            addKeysToInput(in, readPad(player), p, ctl);

            // when returning from pause, catch up the speed control
            if (curTime - lastTime > 10
                || curTime - lastTime < -10) {
              lastTime = curTime;
            }

            {
              boolean irsAttempted = (p.sounds.contains(LJSND.SPAWN) || p.sounds.contains(LJSND.HOLD))
                                 && in.rotation != 0 && ctl.initialRotate != 0;

              // The next line does ALL the game logic.
              LJBits updatedRows = Lockjaw.frame(p, in).add(Gimmicks.gimmicks(p, ctl));

              v[player].backDirty.add(updatedRows);
              if (irsAttempted && (p.sounds.contains(LJSND.ROTATE))) {
                p.sounds.add(LJSND.IRS);
              }
            }

            // items is a partly view-based gimmick
            if (p.gimmick == LJGM.ITEMS && (p.sounds.contains(LJSND.SPAWN))) {
              v[player].hideNext = (v[player].field.nPieces > 7);
              v[player].frontDirty.dirtyNext = true;
            }

            // five, four, three, two, one
            int curCountdown = ctl.countdown;
            if (p.gimmick == LJGM.ULTRA && p.gameTime >= 10500) {
              curCountdown = (int)(10859 - p.gameTime) / 60;
            } else if (p.gimmick == LJGM.BTYPE) {
              curCountdown = 40 - p.lines;
            } else if (p.gimmick == LJGM.BABY) {
              curCountdown = (309 - v[player].control.presses) / 10;
            } else if (p.gimmick == LJGM.DRILL && (p.sounds.contains(LJSND.LINE))) {
              int line = Lockjaw.bfffo(p.tempRows);
              if (line < curCountdown)
              curCountdown = line;
            }

            // we have to wait for the new piece to come out
            // so that the score is credited properly
            if (curCountdown <= 0
                && (p.state == LJState.NEW_PIECE
                    || p.state == LJState.FALLING
                    || p.state == LJState.LANDED)) {
              p.state = LJState.GAMEOVER;
            }

            playSoundEffects(v[player], p.sounds, curCountdown);
            ctl.countdown = curCountdown;

            // Update speedometer
            if (p.sounds.contains(LJSND.LOCK)) {
              for (int i = LJView.N_SPEED_METER_PIECES - 2; i >= 0; --i) {
                v[player].lockTime[i + 1] = v[player].lockTime[i];
              }
              v[player].lockTime[0] = (int)p.gameTime;
              if (v[player].nLockTimes < LJView.N_SPEED_METER_PIECES) {
                ++v[player].nLockTimes;
              }
              v[player].frontDirty.dirtyScore();
            }

            // If the piece was just spawned, move the trail to the piece's
            // starting position and redraw next pieces.
            if (p.sounds.contains(LJSND.SPAWN) || p.sounds.contains(LJSND.HOLD)) {
              v[player].trailY = p.y;
              v[player].frontDirty.dirtyNext();
            }
          }

          ++lastTime;
        }

        for (int player = 0; player < nPlayers; ++player) {
          final LJField p = v[player].field;
          if (p.state == LJState.GAMEOVER && v[player].hidePF) {
            v[player].hidePF = false;
            v[player].backDirty.add((1 << LJ_PF_VIS_HT) - 1);
          }

          updField(v[player], v[player].backDirty);
          v[player].frontDirty.add(v[player].backDirty);
          v[player].backDirty.clear();

          // If piece is falling or landed, and it wasn't just spawned,
          // draw the piece and its shadow.

          if (p.sounds.contains(LJSND.SPAWN) || p.sounds.contains(LJSND.HOLD)) {
            // piece was just spawned, so don't draw the piece
          }
          // Otherwise, if the piece is falling or landed, draw it.
          else if (p.state == LJState.FALLING || p.state == LJState.LANDED
                    || p.sounds.contains(LJSND.LOCK)) {
            if (v[player].hideShadow != LJSHADOW.NO_FALLING) {
              if (p.state == LJState.FALLING && v[player].hideShadow.isColored()) {
                drawShadow(v[player]);
              }
              drawFallingPiece(v[player]);
            }
          }

          ljBeginDraw(v[player], getTime() - lastTime < 2);
          drawScore(v[player]);
          blitField(v[player]);
          ljEndDraw(v[player]);
        }
      }

/*
//#if WITH_REPLAY
  {
    int player = 0;
    if (v[player].control.replaySrc != null) {
      replayClose(v[player].control.replaySrc);
      v[player].control.replaySrc = null;
    }
    if (v[player].control.replayDst != null) {
      replayClose(v[player].control.replayDst);
      v[player].control.replayDst = null;
    }
  }
//#endif
*/
    }

}
